package org.metasketch.editor.toolbox;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.plaf.basic.BasicListUI;

import org.metasketch.diagram.javafx.graphics.BaseGraphic;
import org.metasketch.diagram.javafx.graphics.BaseLink;
import org.metasketch.diagram.javafx.graphics.DefaultLink;
import org.metasketch.editor.links.DefaultLinkHandler;
import org.metasketch.editor.links.GraphicalEndConfig;
import org.metasketch.editor.links.GraphicalLink;
import org.metasketch.editor.links.GraphicalLinkConfig;
import org.metasketch.editor.links.LinkEndOrientation;
import org.metasketch.editor.links.LinkRegistry;
import org.metasketch.editor.links.SemanticConfig;
import org.metasketch.editor.links.SemanticLink;
import org.metasketch.editor.links.SemanticLinkConfig;
import org.metasketch.editor.timeline.SnapshotManager;
import org.metasketch.editor.toolbox.advanced.DefaultCalloutHandler;
import org.metasketch.editor.toolbox.advanced.DropHandlerRegistry;
import org.metasketch.editor.toolbox.advanced.IDragSource;
import org.metasketch.editor.toolbox.advanced.IDropHandler;
import org.metasketch.editor.toolbox.advanced.IRealDropHandler;
import org.metasketch.graphics.GraphicContainer;
import org.metasketch.graphics.Point;
import org.metasketch.graphics.registry.GraphicsRegistry;
import org.metasketch.javafx.graphics.IJFXGraphicNode;
import org.metasketch.javafx.graphics.JFXDiagram;
import org.metasketch.model.BaseModel;
import org.metasketch.model.CommonIdentified;
import org.metasketch.reflection.RefUtil;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseEvent;
import javafx.stage.WindowEvent;

public abstract class SimpleToolBaseDiagramType extends BaseDiagramType
		implements ChangeListener<Toggle>, EventHandler<ActionEvent>,
		IDropHandler {

	protected ContextMenu contextMenu;

	protected GraphicsRegistry graphicsRegistry = new GraphicsRegistry();

	// protected InstanceCreatorRegistry instanceCreatorRegistry = new
	// InstanceCreatorRegistry();

	protected ToolHandlerRegistry toolHandlerRegistry = new ToolHandlerRegistry();

	protected ToolNameRegistry toolNameRegistry = new ToolNameRegistry();

	protected DropHandlerRegistry dropHandlerRegistry = new DropHandlerRegistry();

	protected DefaultToolInputHandler inputHandler = new DefaultToolInputHandler(
			this);

	protected RelationManager relationManager = new RelationManager();

	protected LinkRegistry linkRegistry = new LinkRegistry();

	protected Map<ToggleButton, String> buttonTool = new HashMap<ToggleButton, String>();

	protected PopupMenuRegistry popupMenuRegistry = new PopupMenuRegistry();

	protected ToggleGroup group;

	private IJFXGraphicNode selectedObject;

	public SimpleToolBaseDiagramType(String name,
			DefaultDiagramBuilder diagramBuilder) {
		super(name, diagramBuilder);

		diagramBuilder.setDiagramType(this);

	}

	@Override
	public void setDragSource(IDragSource dragSource) {
		inputHandler.setDragSource(dragSource);
		inputHandler.setDropHandler(this);
		dragSource.setDropHandler(this);
	}

	public void handleDrop(CommonIdentified semantic, Object dropTarget,
			MouseEvent event, JFXDiagram jfxDiagram, BaseModel model) {
		try {
			System.out.println("Handling drop: " + semantic + " in "
					+ dropTarget);

			// TODO get a drophandler from dropHandlerRegistry and process it
			// locally instead...
			IRealDropHandler dropHandler = dropHandlerRegistry.get(semantic);
			
			if (dropHandler == null) {
				dropHandler = dropHandlerRegistry.getInherited(semantic);
			}
			
			if (dropHandler != null) {
				dropHandler.handleDrop(this, semantic, dropTarget, event,
						jfxDiagram, model);
				event.consume();
				return;
			}

			IToolHandler tool = toolHandlerRegistry.get(semantic);
			// IToolHandler tool = toolHandlerRegistry.get(semantic.getClass());

			if (tool == null)
				return;

			if (tool instanceof DefaultToolHandler) {
				DefaultToolHandler dtool = (DefaultToolHandler) tool;

				dtool.treatCreationEvent(event, jfxDiagram, model, dropTarget,
						semantic, true);

			} else if (tool instanceof DefaultLinkHandler) {
				DefaultLinkHandler dtool = (DefaultLinkHandler) tool;

				ObservableList<Node> nodes = jfxDiagram.getNodes()
						.getChildren();

				RefUtil util = new RefUtil();

				CommonIdentified source = null;
				CommonIdentified target = null;

				Point mousePoint = new Point((int) event.getX(),
						(int) event.getY());

				Object mouseTarget = event.getSource();
				if (mouseTarget instanceof BaseGraphic) {
					BaseGraphic bmouse = (BaseGraphic) mouseTarget;
					mousePoint = bmouse.getCompleteCoordinatesThroughParent(
							(int) event.getX(), (int) event.getY());
					// mouseX = point.getX();
					// mouseY = point.getY();
				}

				// link semantic object must have a target and source fields
				// with these names. Otherwise, no way of telling.
				source = (CommonIdentified) util.getValue(semantic, "source");
				target = (CommonIdentified) util.getValue(semantic, "target");

				// can't do nothing without both a source and a target
				if (source == null || target == null)
					return;

				BaseGraphic sourceGraphic = null;
				BaseGraphic targetGraphic = null;

				// Double sourceDistance = null;
				// Double targetDistance = null;

				sourceGraphic = jfxDiagram.getClosestNode(mousePoint, source);
				targetGraphic = jfxDiagram.getClosestNode(mousePoint, target);

				// there's nothing to do if the source and target graphics are
				// not already present in the diagram
				if (sourceGraphic == null || targetGraphic == null)
					return;

				dtool.buildLink(sourceGraphic, targetGraphic, jfxDiagram,
						model, semantic, true);

				// dtool.treatCreationEvent(event, jfxDiagram, model,
				// dropTarget,
				// semantic);

			}

		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	@Override
	public void changed(ObservableValue<? extends Toggle> arg0, Toggle arg1,
			Toggle arg2) {

		// arg2.
		//
		// currentTool =
		ToggleButton button = null;

		if (arg2 == null && arg1 != null)
			button = (ToggleButton) arg1;
		else
			button = (ToggleButton) arg2;

		String toolType = buttonTool.get(button);

		System.out.println("Selected Tool type: " + toolType);

		IToolHandler toolHandler = null;
		try {
			toolHandler = toolHandlerRegistry.get(toolType);
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		inputHandler.setToolHandler(toolHandler);

	}

	@Override
	public ToolBar getToolbar() {

		group = new ToggleGroup();
		group.selectedToggleProperty().addListener(this);

		ToolBar toolbar = createToolbar();

		// toolbar.get

		return toolbar;
	}

	protected abstract ToolBar createToolbar();

	protected void registerCallout(String name, String icon,
			String graphicBaseName, String sourceGraphicName,
			Class semanticType, Class nodeGraphic, Class linkGraphic,
			boolean useSourceSemantic) {

		// main tool
		registerTool(name, icon, graphicBaseName, new DefaultCalloutHandler(
				graphicBaseName, this, useSourceSemantic), null);

		// setup and register the node part
		DefaultToolHandler tool = new DefaultToolHandler(semanticType,
				nodeGraphic, graphicBaseName + "-Node", true, relationManager);

		registerTool(name + "-Node", icon, graphicBaseName + "-Node", tool,
				nodeGraphic);

		// setup and register the link part
		GraphicalLinkConfig linkConfigB = new GraphicalLinkConfig();
		linkConfigB.setSourceTypes(sourceGraphicName);
		linkConfigB.getSource().getLinkEnd()
				.setOrientation(LinkEndOrientation.CLOSEST);

		linkConfigB.setTargetTypes(graphicBaseName + "-Node");
		linkConfigB.getTarget().getLinkEnd()
				.setOrientation(LinkEndOrientation.CLOSEST);

		if (linkGraphic == null)
			registerDefaultGraphicalLink(name + "-Link", graphicBaseName
					+ "-Link", null, linkConfigB);
		else
			registerGraphicalLink(name + "-Link", graphicBaseName + "-Link",
					linkGraphic, null, linkConfigB);

	}

	protected void registerCalloutExt(String name, String icon,
			String graphicType, String nodeGraphicBaseName,
			String linkGraphicName) {

		DefaultCalloutHandler callout = new DefaultCalloutHandler(
				nodeGraphicBaseName, linkGraphicName, this);

		callout.setUseSourceSemantic(false);

		// main tool
		registerTool(name, icon, graphicType, callout, null);
	}

	protected void registerCalloutExt(String name, String icon,
			String graphicType, Class nodeType, Class linkType) {

		DefaultCalloutHandler callout = new DefaultCalloutHandler(nodeType,
				linkType, this);

		callout.setUseSourceSemantic(false);

		// main tool
		registerTool(name, icon, graphicType, callout, null);
	}

	protected void registerTool(String name, String icon, String graphicType,
			IToolHandler toolHandler, Class graphic) {

		toolNameRegistry.register(graphicType, name, icon);

		if (graphic != null)
			graphicsRegistry.register(graphicType, graphic);

		// if (instanceCreator != null)
		// instanceCreatorRegistry.register(graphicType, instanceCreator);

		if (toolHandler != null) {
			toolHandlerRegistry.register(graphicType, toolHandler);

			if (toolHandler instanceof DefaultToolHandler) {
				DefaultToolHandler dtool = (DefaultToolHandler) toolHandler;

				if (dtool.getProperSemanticType() != null)
					toolHandlerRegistry.register(dtool.getProperSemanticType(),
							toolHandler);

			}

		}

	}

	protected GraphicalLink registerGraphicalLink(String name,
			String graphicType, Class graphic, SemanticConfig semanticConfig,
			GraphicalLinkConfig... linkConfigs) {

		GraphicalLink link = new GraphicalLink();
		link.setSemanticConfig(semanticConfig);

		link.getGraphicalLinkConfigs().addAll(Arrays.asList(linkConfigs));
		// if (source != null)
		// link.setSource(source);
		//
		// if (target != null)
		// link.setTarget(target);

		if (graphic != null)
			graphicsRegistry.register(graphicType, graphic);

		toolNameRegistry.register(graphicType, name, null);

		DefaultLinkHandler dlink = new DefaultLinkHandler(link, graphic,
				graphicType);

		toolHandlerRegistry.register(graphicType, dlink);

		if (semanticConfig != null
				&& semanticConfig.getProperSemanticType() != null)
			toolHandlerRegistry.register(
					semanticConfig.getProperSemanticType(), dlink);

		linkRegistry.register(graphicType, link);

		return link;

	}

	protected void setupSemanticTestScrip(IToolHandler tool,
			String semanticTestScript) {
		DefaultToolHandler dtool = (DefaultToolHandler) tool;
		dtool.setSemanticTestScript(semanticTestScript);

	}

	protected GraphicalLink registerDefaultGraphicalLink(String name,
			String graphicType, SemanticConfig semanticConfig,
			GraphicalLinkConfig... linkConfigs) {
		return registerGraphicalLink(name, graphicType, DefaultLink.class,
				semanticConfig, linkConfigs);
	}

	protected SemanticLink registerSemanticLink(String name,
			String graphicType, Class graphic, SemanticConfig semanticConfig,
			SemanticLinkConfig... linkConfigs) {

		SemanticLink link = new SemanticLink();
		link.setSemanticConfig(semanticConfig);

		link.getSemanticLinkConfigs().addAll(Arrays.asList(linkConfigs));
		// if (source != null)
		// link.setSource(source);
		//
		// if (target != null)
		// link.setTarget(target);

		if (graphic != null)
			graphicsRegistry.register(graphicType, graphic);

		toolNameRegistry.register(graphicType, name, null);

		DefaultLinkHandler dlink = new DefaultLinkHandler(link, graphic,
				graphicType);

		toolHandlerRegistry.register(graphicType, dlink);

		if (semanticConfig != null
				&& semanticConfig.getProperSemanticType() != null)
			toolHandlerRegistry.register(
					semanticConfig.getProperSemanticType(), dlink);

		linkRegistry.register(graphicType, link);

		return link;

	}

	protected SemanticLink registerDefaultSemanticLink(String name,
			String graphicType, SemanticConfig semanticConfig,
			SemanticLinkConfig... linkConfigs) {
		return registerSemanticLink(name, graphicType, DefaultLink.class,
				semanticConfig, linkConfigs);
	}

	protected IToolHandler registerToolClasses(String name, String icon,
			String graphicType, Class semanticType, Class graphic,
			boolean createOverEmptySpace) {

		DefaultToolHandler tool = new DefaultToolHandler(semanticType, graphic,
				graphicType, createOverEmptySpace, relationManager);

		registerTool(name, icon, graphicType, tool, graphic);

		return tool;

	}

	protected IToolHandler registerToolClasses(String name, String icon,
			String graphicType, ISemanticFactory factory, Class graphic,
			boolean createOverEmptySpace) {

		DefaultToolHandler tool = new DefaultToolHandler(factory, graphic,
				graphicType, createOverEmptySpace, relationManager);

		registerTool(name, icon, graphicType, tool, graphic);

		return tool;

	}

	protected IToolHandler registerToolClasses(String name, String icon,
			String graphicType, String script, Class graphic,
			boolean createOverEmptySpace) {

		DefaultToolHandler tool = new DefaultToolHandler(script, graphic,
				graphicType, createOverEmptySpace, relationManager);

		registerTool(name, icon, graphicType, tool, graphic);

		return tool;

	}

	protected IToolHandler registerToolClasses(String name, String icon,
			String graphicType, Class semanticType, String script,
			Class graphic, boolean createOverEmptySpace) {

		DefaultToolHandler tool = new DefaultToolHandler(semanticType, script,
				graphic, graphicType, createOverEmptySpace, relationManager);

		registerTool(name, icon, graphicType, tool, graphic);

		return tool;

	}

	protected ToggleButton getTypeButton(String instanceType) {

		ToggleButton button = createButton(
				toolNameRegistry.getName(instanceType), instanceType);
		buttonTool.put(button, instanceType);

		return button;

	}

	protected ToggleButton createButton(String name, String data) {
		return createButton(name, group, data);
	}

	public GraphicsRegistry getGraphicsRegistry() {
		return graphicsRegistry;
	}

	// public InstanceCreatorRegistry getInstanceCreatorRegistry() {
	// return instanceCreatorRegistry;
	// }

	public ToolHandlerRegistry getToolHandlerRegistry() {
		return toolHandlerRegistry;
	}

	@Override
	public IInputHandler getInputHandler() {
		return inputHandler;
	}

	public RelationManager getRelationManager() {
		return relationManager;
	}

	public LinkRegistry getLinkRegistry() {
		return linkRegistry;
	}

	public void addDefaultDeleteFromDiagramGraphicalPopupMenuOption(String type) {
		popupMenuRegistry.addGraphicalMenuEntry(type, new PopupMenuEntry(
				"Delete from Diagram", "graphic.removeFromDiagram();"));
	}

	public void addDefaultDeleteFromDiagramSemanticPopupMenuOption(Class type) {
		popupMenuRegistry.addSemanticalMenuEntry(type, new PopupMenuEntry(
				"Delete from Diagram", "graphic.removeFromDiagram();"));
	}

	public void addDefaultDeleteFromDiagramInheritedPopupMenuOption(Class type) {
		popupMenuRegistry.addInheritedSemanticalMenuEntry(type,
				new PopupMenuEntry("Delete from Diagram",
						"graphic.removeFromDiagram();"));
	}

	public void addGraphicalMenuEntry(String type, String name, String script) {
		popupMenuRegistry.addGraphicalMenuEntry(type, new PopupMenuEntry(name,
				script));
	}

	public void addGraphicalMenuEntry(String type, String name,
			String testScript, String script) {
		popupMenuRegistry.addGraphicalMenuEntry(type, new PopupMenuEntry(name,
				testScript, script));
	}

	public void addGraphicalMenuEntry(String type, String name,
			String testScript, String script, String semanticalScript,
			String graphicalScript) {
		popupMenuRegistry.addGraphicalMenuEntry(type, new PopupMenuEntry(name,
				testScript, script, semanticalScript, graphicalScript));
	}

	public void addSemanticalMenuEntry(Class type, String name, String script) {
		popupMenuRegistry.addSemanticalMenuEntry(type, new PopupMenuEntry(name,
				script));
	}

	public void addSemanticalMenuEntry(Class type, String name,
			String testScript, String script) {
		popupMenuRegistry.addSemanticalMenuEntry(type, new PopupMenuEntry(name,
				testScript, script));
	}

	public void addSemanticalMenuEntry(Class type, String name,
			String testScript, String script, String semanticalScript,
			String graphicalScript) {
		popupMenuRegistry.addSemanticalMenuEntry(type, new PopupMenuEntry(name,
				testScript, script, semanticalScript, graphicalScript));
	}

	public void addInheritedSemanticalMenuEntry(Class type, String name,
			String script) {
		popupMenuRegistry.addInheritedSemanticalMenuEntry(type,
				new PopupMenuEntry(name, script));
	}

	public void addInheritedSemanticalMenuEntry(Class type, String name,
			String testScript, String script) {
		popupMenuRegistry.addInheritedSemanticalMenuEntry(type,
				new PopupMenuEntry(name, testScript, script));
	}

	public void addInheritedSemanticalMenuEntry(Class type, String name,
			String testScript, String script, String semanticalScript,
			String graphicalScript) {
		popupMenuRegistry.addInheritedSemanticalMenuEntry(type,
				new PopupMenuEntry(name, testScript, script, semanticalScript,
						graphicalScript));
	}

	public void processSecondaryMenu(MouseEvent event) {
		cleanInterface();

		Object source = event.getSource();

		selectedObject = (IJFXGraphicNode) source;

		contextMenu = new ContextMenu();
		contextMenu.setAutoHide(true);

		contextMenu.setOnShowing(new EventHandler<WindowEvent>() {
			public void handle(WindowEvent e) {
				System.out.println("showing");
			}
		});
		contextMenu.setOnShown(new EventHandler<WindowEvent>() {
			public void handle(WindowEvent e) {
				System.out.println("shown");
			}
		});

		contextMenu.setOnAutoHide(new EventHandler<Event>() {
			public void handle(Event e) {
				System.out.println("hidding");
				cleanInterface();
			}
		});

		List<PopupMenuEntry> entries = null;

		if (source instanceof BaseGraphic)
			entries = popupMenuRegistry.getEntries((BaseGraphic) source);
		else if (source instanceof BaseLink)
			entries = popupMenuRegistry.getEntries((BaseLink) source);

		if (entries.size() == 0) {
			event.consume();
			return;
		}

		for (PopupMenuEntry entry : entries) {

			if (entry.getTestScript() != null) {
				if (!isValid(entry.getTestScript()))
					continue;
			}

			MenuItem item1 = new MenuItem(entry.getName());
			item1.setUserData(entry);
			item1.setOnAction(this);
			contextMenu.getItems().add(item1);
		}

		// .addAll(entries);

		contextMenu.show((Node) source, event.getScreenX(), event.getScreenY());

		event.consume();
	}

	public void cleanInterface() {
		if (contextMenu != null && contextMenu.isShowing()) {
			contextMenu.hide();
			contextMenu = null;
		}

		selectedObject = null;
	}

	@Override
	public void handle(ActionEvent event) {
		Object source = event.getSource();
		if (source != null && source instanceof MenuItem
				&& selectedObject != null) {

			MenuItem menuItem = (MenuItem) source;

			PopupMenuEntry entry = (PopupMenuEntry) menuItem.getUserData();

			String script = entry.getScript();
			if (script != null) {
				GroovyShell shell = getBindedGroovyShell(selectedObject);
				shell.evaluate(script);
			}

			String semanticScript = entry.getSemanticScript();
			if (semanticScript != null) {
				GroovyShell shell = getBindedGroovyShell(selectedObject);
				shell.evaluate(semanticScript);
			}

			String graphicalScript = entry.getGraphicalScript();
			if (graphicalScript != null) {
				// GroovyShell shell = getBindedGroovyShell();
				// shell.evaluate(graphicalScript);

				// apply the script to all similar objects with the same
				// semantic object
				List graphicalListeners = selectedObject
						.getModelChangeManager().getListeners(
								selectedObject.getSemantic(),
								selectedObject.getClass());
				// IJFXGraphicNode temp = selectedObject;
				for (Object object : graphicalListeners) {
					// selectedObject = ;
					GroovyShell shell = getBindedGroovyShell((IJFXGraphicNode) object);
					shell.evaluate(graphicalScript);
				}

				// selectedObject = temp;

			}

			if (selectedObject != null)
				selectedObject.alertModelChanged();

			cleanInterface();

			SnapshotManager.takeSnapshot();
		}
	}

	private GroovyShell getBindedGroovyShell(IJFXGraphicNode node) {
		Binding binding = new Binding();
		binding.setVariable("graphic", node);
		binding.setVariable("semantic", node.getSemantic());

		if (node instanceof BaseLink) {
			BaseLink link = (BaseLink) node;

			BaseGraphic sourceGraphic = link.getSource();
			BaseGraphic targetGraphic = link.getTarget();

			binding.setVariable("sourceGraphic", sourceGraphic);
			binding.setVariable("targetGraphic", targetGraphic);

			if (sourceGraphic != null)
				binding.setVariable("sourceSemantic",
						sourceGraphic.getSemantic());

			if (targetGraphic != null)
				binding.setVariable("targetSemantic",
						targetGraphic.getSemantic());
		}

		GroovyShell shell = new GroovyShell(binding);
		return shell;
	}

	protected boolean isValid(String testScript) {
		GroovyShell shell = getBindedGroovyShell(selectedObject);

		// String script = (String) menuItem.getUserData();

		Boolean result = (Boolean) shell.evaluate(testScript);

		return result;
	}

	public ToggleGroup getGroup() {
		return group;
	}

	public ToolNameRegistry getToolNameRegistry() {
		return toolNameRegistry;
	}

	public PopupMenuRegistry getPopupMenuRegistry() {
		return popupMenuRegistry;
	}

	public DropHandlerRegistry getDropHandlerRegistry() {
		return dropHandlerRegistry;
	}

}
